/**
 * ***************************************************************************** Copyright (c) 2003,
 * 2006 Subclipse project and others. All rights reserved. This program and the accompanying
 * materials are made available under the terms of the Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * <p>Contributors: Subclipse project committers - initial API and implementation
 * ****************************************************************************
 */
package org.tigris.subversion.subclipse.core;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.team.core.ProjectSetCapability;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.RepositoryProviderType;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.subscribers.Subscriber;
import org.tigris.subversion.subclipse.core.client.PeekStatusCommand;
import org.tigris.subversion.subclipse.core.resources.SVNWorkspaceRoot;
import org.tigris.subversion.subclipse.core.sync.SVNWorkspaceSubscriber;
import org.tigris.subversion.svnclientadapter.ISVNClientAdapter;

/** This class represents the SVN Provider's capabilities in the absence of a particular project. */
public class SVNTeamProviderType extends RepositoryProviderType {

  protected static AutoShareJob autoShareJob;

  public static class AutoShareJob extends Job {

    List<IProject> projectsToShare = new ArrayList<IProject>();

    AutoShareJob() {
      super("Auto-sharing imported subversion projects");
    }

    public boolean isQueueEmpty() {
      return projectsToShare.isEmpty();
    }

    /* (non-Javadoc)
     * @see org.eclipse.core.runtime.jobs.Job#shouldSchedule()
     */
    public boolean shouldSchedule() {
      return !isQueueEmpty();
    }

    /* (non-Javadoc)
     * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
     */
    public boolean shouldRun() {
      synchronized (projectsToShare) {
        for (Iterator<IProject> iter = projectsToShare.iterator(); iter.hasNext(); ) {
          IProject project = iter.next();
          if (RepositoryProvider.isShared(project)) {
            iter.remove();
          }
        }
        return !projectsToShare.isEmpty();
      }
    }

    public void share(IProject project) {
      if (!RepositoryProvider.isShared(project)) {
        synchronized (projectsToShare) {
          if (!projectsToShare.contains(project)) {
            SVNWorkspaceRoot.setManagedBySubclipse(project);
            projectsToShare.add(project);
          }
        }
        if (getState() == Job.NONE && !isQueueEmpty()) schedule();
      }
    }

    /* (non-Javadoc)
     * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
     */
    protected IStatus run(IProgressMonitor monitor) {
      IProject next = null;
      next = getNextProject();
      monitor.beginTask(null, IProgressMonitor.UNKNOWN);
      while (next != null) {
        if (next.isAccessible()) {
          autoconnectSVNProject(next, Policy.subMonitorFor(monitor, IProgressMonitor.UNKNOWN));
        }
        next = getNextProject();
      }
      monitor.done();
      return Status.OK_STATUS;
    }

    private IProject getNextProject() {
      IProject next = null;
      synchronized (projectsToShare) {
        if (!projectsToShare.isEmpty()) {
          next = (IProject) projectsToShare.remove(0);
        }
      }
      return next;
    }

    /*
     * Auto-connect to the repository using .svn/ directories
     */
    private void autoconnectSVNProject(IProject project, IProgressMonitor monitor) {
      try {
        PeekStatusCommand command = new PeekStatusCommand(project);
        try {
          command.execute();
        } catch (SVNException e1) {
          if (e1.getMessage() != null
              && e1.getMessage().contains(SVNProviderPlugin.UPGRADE_NEEDED)) {
            if (!SVNProviderPlugin.handleQuestion(
                "Upgrade Working Copy",
                project.getName()
                    + " appears to be managed by Subversion, but the working copy needs to be upgraded.  Do you want to upgrade the working copy now?\n\nWarning:  This operation cannot be undone.")) {
              return;
            }
          }
          SVNWorkspaceRoot.upgradeWorkingCopy(project, monitor);
        }
        SVNWorkspaceRoot.setSharing(project, monitor);
      } catch (TeamException e) {
        SVNProviderPlugin.log(
            IStatus.ERROR, "Could not auto-share project " + project.getName(), e); // $NON-NLS-1$
      }
    }
  }

  public static synchronized AutoShareJob getAutoShareJob() {
    if (autoShareJob == null) {
      autoShareJob = new AutoShareJob();
      autoShareJob.addJobChangeListener(
          new JobChangeAdapter() {
            public void done(IJobChangeEvent event) {
              // Reschedule the job if it has unprocessed projects
              if (!autoShareJob.isQueueEmpty()) {
                autoShareJob.schedule();
              }
            }
          });
      autoShareJob.setSystem(true);
      autoShareJob.setPriority(Job.SHORT);
      // Must run with the workspace rule to ensure that projects added while we're running
      // can be shared
      autoShareJob.setRule(ResourcesPlugin.getWorkspace().getRoot());
    }
    return autoShareJob;
  }

  /** @see org.eclipse.team.core.RepositoryProviderType#supportsProjectSetImportRelocation() */
  public boolean supportsProjectSetImportRelocation() {
    return false;
  }

  /* (non-Javadoc)
   * @see org.eclipse.team.core.RepositoryProviderType#getProjectSetCapability()
   */
  public ProjectSetCapability getProjectSetCapability() {
    return new SVNProjectSetCapability();
  }

  public static class AutoAddJob extends Job {
    static final int MAX_RETRIES = 10;
    int reschedCount = 0;
    final IProject project;

    protected AutoAddJob(IProject project) {
      super("Auto-adding newly created project to subversion: " + project.getName()); // $NON-NLS-1$
      this.project = project;
    }

    /* (non-Javadoc)
     * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
     */
    protected IStatus run(IProgressMonitor monitor) {
      monitor.beginTask(null, IProgressMonitor.UNKNOWN);
      SVNProviderPlugin plugin = SVNProviderPlugin.getPlugin();
      SVNClientManager svnClientManager = plugin.getSVNClientManager();
      ISVNClientAdapter client = null;
      try {

        if (plugin == null || plugin.getSimpleDialogsHelper() == null) {
          if (++reschedCount > MAX_RETRIES) {
            String errorString =
                "Subclipse core and/or ui didn't come up in "
                    + MAX_RETRIES
                    + " retries, failing."; //$NON-NLS-1$
            System.err.println(errorString); // Let it be visible to the user
            throw new SVNException(errorString);
          }
          schedule(1000);
          return Status.OK_STATUS;
        }

        if (!plugin
            .getSimpleDialogsHelper()
            .promptYesNo(
                "Auto-add " + project.getName() + " to source control", // $NON-NLS-1$
                "The new project \""
                    + project.getName()
                    + "\" was created in a subversion "
                    + //$NON-NLS-1$
                    "controlled directory.\n\n"
                    + //$NON-NLS-1$
                    "Would you like to automatically add it to source control?",
                true)) { //$NON-NLS-1$

          return Status.OK_STATUS;
        }

        client = svnClientManager.getSVNClient();

        File file = project.getLocation().toFile();
        client.addDirectory(file, false);

        RepositoryProvider.map(project, SVNProviderPlugin.getTypeId());
        plugin.getStatusCacheManager().refreshStatus(project, true);

      } catch (Exception e) {
        SVNProviderPlugin.log(
            IStatus.ERROR, "Could not auto-add project " + project.getName(), e); // $NON-NLS-1$
        return Status.CANCEL_STATUS;
      } finally {
        monitor.done();
        svnClientManager.returnSVNClient(client);
      }
      return Status.OK_STATUS;
    }
  }

  /** Create and schedule an auto-add job */
  private static synchronized void createAutoAddJob(IProject project) {
    Job j = new AutoAddJob(project);
    j.setSystem(true);
    j.setPriority(Job.SHORT);
    j.setRule(ResourcesPlugin.getWorkspace().getRoot());
    j.schedule();
  }

  /* (non-Javadoc)
   * @see org.eclipse.team.core.RepositoryProviderType#metaFilesDetected(org.eclipse.core.resources.IProject, org.eclipse.core.resources.IContainer[])
   */
  public void metaFilesDetected(IProject project, IContainer[] containers) {
    SVNProviderPlugin plugin = SVNProviderPlugin.getPlugin();
    boolean isProject = false;
    boolean isSvnProject = plugin.isManagedBySubversion(project);

    for (int i = 0; i < containers.length; i++) {
      IContainer container = containers[i];
      IContainer svnDir = null;

      if (!isProject && container.getType() == IResource.PROJECT) isProject = true;

      if (plugin.isAdminDirectory(container.getName())) { // $NON-NLS-1$
        svnDir = container;
      } else {
        IResource resource = container.findMember(plugin.getAdminDirectoryName()); // $NON-NLS-1$
        if (resource != null && resource.getType() != IResource.FILE) {
          svnDir = (IContainer) resource;
        }
      }
      try {
        if (svnDir != null && !svnDir.isTeamPrivateMember()) {
          if (!isSvnProject) {
            if (plugin.isManagedBySubversion(svnDir.getParent())) svnDir.setTeamPrivateMember(true);
          } else {
            svnDir.setTeamPrivateMember(true);
          }
        }
      } catch (CoreException e) {
        SVNProviderPlugin.log(
            IStatus.ERROR,
            "Could not flag meta-files as team-private for " + svnDir.getFullPath(),
            e); //$NON-NLS-1$
      }
    }

    if (!isProject) return; // Nothing more to do, all remaining operations are on projects

    // Examine whether this project is a nested project. If yes, we don't
    // share it automatically.
    if (!SVNProviderPlugin.getPlugin()
            .getPluginPreferences()
            .getBoolean(ISVNCoreConstants.PREF_SHARE_NESTED_PROJECTS)
        && isNestedProject(project)) {
      return;
    }

    if (isSvnProject) {
      // It's a project and has toplevel .svn directory, lets share it!
      getAutoShareJob().share(project);
    } else {
      // It's a project and doesn't have .svn dir, let's see if we can add it!
      IPath parentDir = project.getLocation().append("../"); // $NON-NLS-1$

      if (plugin.isManagedBySubversion(parentDir)) {
        createAutoAddJob(project);
      }
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.team.core.RepositoryProviderType#getSubscriber()
   */
  public Subscriber getSubscriber() {
    return SVNWorkspaceSubscriber.getInstance();
  }

  private boolean isNestedProject(IProject testProject) {
    IPath testProjectLocation = testProject.getLocation();

    IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
    for (int i = 0; i < projects.length; i++) {
      IProject project = projects[i];

      if (project.equals(testProject)) continue;

      IPath projectLocation = project.getLocation();
      if ((projectLocation != null) && projectLocation.isPrefixOf(testProjectLocation)) return true;
    }

    return false;
  }
}
